package singleLinkedList;

// 无头单向非循环链表模拟实现

// 注意 任意位置插入（√）、清空操作 以及 删除关键字（单个√）、（all√）

import java.util.Stack;

// 1、无头单向非循环链表实现
public class SingleLinkedList {
    public Node head; // 定义头结点--重要！！一定要有才可以找到头结点！

    public void createList() {
        //通过穷举的方式 创建一个链表出来
        Node node1 = new Node(8);
        Node node2 = new Node(2);
        Node node3 = new Node(1998);
        node1.next=node2;
        node2.next=node3;
        head = node1;  //注意该赋值方式：相当于把next赋值，赋值后指向node1，但是val不赋值，默认值just
    }


    //头插法: 时间复杂度O(1)
    // 头插法：修改头结点的指向：node.next=head;  head = node;即：先绑住后面！
    public void addFirst(int data) {
        // 首先进行新数据的节点创建
        Node node = new Node(data);
        node.next = this.head;
        this.head = node; // 注意一定要写这一步！！ 头结点完成变换！！
    }


    //尾插法：尾插时间复杂度O(N)
    // 尾插法需要进行头结点是否为null的判断
    public void addLast(int data) {
        // 首先进行新数据的节点创建
        Node node = new Node(data);
        if(this.head == null) {
            head = node;  //注意此处是否正确
            return ;
        } else {
            Node cur = this.head;  // 要创建临时变量，保持头结点位置不变！！!
            while(cur.next != null) { //cur.next==null：指向最后一个结点位置
                cur = cur.next;
            }
            cur.next = node;
        }
    }

    // 检查下标是否合法
    // 错误示范：返回类型！！ 实际应该不返回，下标不合法则抛出异常
    /*private boolean checkIndex(int index) {
    }*/

    private void checkIndex(int index) {
        if(index<0 || index>size()) { // 此时可以等于把长度是因为增加节点可以到该长度！
            throw new IndexException("sorry 下标不合法！");
        }
    }

    // 拿到index之前的结点-写一个方法找到 index-1 位置上的节点：需要走index-1次就可以走到index-1位置
    private Node findIndexOfPre(int index) {
        Node cur = this.head;
        while(index-1 !=0) {
            cur = cur.next;
            // 一定要注意循环条件的改变！！
            index--;
        }
        // 出来就说明已经循环拿到了想要的值
        return cur;
    }


    //任意位置插入,第一个数据节点为0号下标
    public boolean addIndex(int index,int data) {
        // 首先要检查下标是否合法
        checkIndex(index);
        Node node = new Node(data);
        // 如果是0，则进行头插
        if(index==0) {
            addFirst(data);
            return true;
        } else if(index==size()) {
            // 如果是长度，则进行尾插
            addLast(data);
            return true;
        } else {
            // 其他则进行另外方法:注意怎么拿到index-1位置的节点--写一个方法
            Node cur = findIndexOfPre(index);
            node.next = cur.next;
            cur.next = node;
            return true;
        }

        // 再有一种方法是直接插入到cur走到的位置，最后再将前驱val和其值进行互换
    }


    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key) {
        // 进行遍历：
        Node cur = this.head;

        // 该方法比较麻烦！
        /*// 必须满足以下两个条件！！
        if(cur == null) { //是否为空的判断
            return false;
        }
        // 注意判断相等，则如直接相等就返回true
        while (cur.val != key && cur != null) {
            cur = cur.next;
        }
        if(cur.val == key) {
            return true;
        } else {
            return false;
        }*/

        // 以下为更加简便的修改！
        while (cur != null) {
            while (cur.val == key) {
                return true;
            }
            // 出来就是不等
            cur = cur.next;
        }
        // 再出来就是有两种情况：==null 以及！=key
        return false;
    }

    // 写一个方法找到前一个结点:注意该方法的书写！！
    private Node preNodeOfKey (int key){
        /*Node cur = this.head;
        Node pre = cur;
        // 这里的循环条件错误！！
        while(cur != null) {
            while(cur.val != key) { //这里循环条件也错误！！！
                cur = cur.next;
                pre = cur;
            }
            // 出来则等于key
            cur = cur.next;
        }
        return pre;*/

        // 正确方法如下：
        Node cur = this.head;
        // 循环条件是只到最后一个结点，并不需要遍历完all！！
        // 就算是删除最后一个结点，也只需要遍历到倒数第二个结点就行！
        // 头节点之前是没有之前节点的，所以此时不用单独考虑头节点的问题
        while(cur.next != null) { // 注意循环条件
            while(cur.next.val==key) { // 注意这里的循环条件：因为头节点单独处理
                return cur;
            }
            // 跳出则说明不相等，不相等就进行条件的变换
            cur = cur.next;
        }
        return cur;
    }


    //删除第一次出现关键字为key的节点
    public void remove(int key) {
        // 以下是错误代码！！
        /*Node cur = this.head;

        // 写一个方法区找到前一个结点
        Node pre = preNodeOfKey(key);

        // 这里有点儿问题！！
        // 找到cur：cur = pre.next;
        pre.next = pre.next.next;
        cur = pre.next;*/

        // 改正：
        // 另外情况处理：头节点如果就是所找的要删除的节点的情况
        if(head.val == key) {
            this.head = head.next;
            return ;  // 不能忘记return； ！！
        }

        Node cur = preNodeOfKey(key);
        // 要进行判断！！！是否为null
        if(cur == null) {
            return ;
        }
        Node del =cur.next;  // 注意此处写法！！
        cur.next = del.next;
        // 或者： cur.next = cur.next.next;

    }


    //删除所有值为key的节点
    // 注意写法！！画图分析！！
    public void removeAllKey(int key) {
        // 进来首先进行头结点是否为空的判断！！
        if(this.head == null) {
            return;
        }

        // 需要一个循环:把整个链表都遍历完
        Node pre = this.head;
        Node cur = this.head.next;
        while (cur != null) {
            if(cur.val == key) {
                pre.next = cur.next;
                cur = cur.next;
            } else {
                pre = cur;
                cur = cur.next;
            }
        }
        // 单独处理头节点的问题，刚刚没有处理头节点问题！！
        if(this.head.val == key) { // 注意是if，不用while循环，因为上面已经处理了所有头结点之后的节点
            this.head = head.next;
        }
        // 注意把头结点的处理放在最后！！：因为如果是从头结点开始连续存在要删除的key值，头结点将会变换至下一个key，也就是会一直保留一个key在头结点位置

        /*// 另一种方法是 创建一个新的头节点！！
        Node head1 = new Node(-1); // 随便存一个值就行
        head1.next = this.head;
        head = head1; // 注意此处的修改！
        // 然后进行遍历处理（不用单独考虑头结点）--上面方法
        Node pre = head;
        Node cur = head.next;
        while (cur != null) {
            if(cur.val == key) {
                pre.next = cur.next;
                cur = cur.next;
            } else {
                pre = cur;
                cur = cur.next;
            }
        }
        head = head.next;  // 注意要将头结点的位置进行变换！！*/

    }

    //得到单链表的长度
    public int size() {
        // 进行链表遍历：直至cur==null则表明遍历完成
        Node cur = this.head;
        int count =0;
        while(cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }


    // 打印链表（全部打印）
    public void display() {
        // 循环判断是否为空null

        // 错误代码：
        /*Node head = this.head;  // 注意一定要首先进行变量创建以及赋值
        while(head != null) {
            Node curNode = head.next;
            System.out.print(curNode.val +" ");
            head = head.next;
        }*/

        // 画图判断修改！！
        Node curNode = this.head;
        while(curNode != null) { // curNode==null时 说明遍历完链表
            System.out.print(curNode.val+" ");
            curNode = curNode.next;
        }
        System.out.println();
    }

    // 重写（指定位置开始打印）
    public void display(Node node) {
        Node curNode = node; // 注意看赋值是否正确！！
        while(curNode != null) { // curNode==null时 说明遍历完链表
            System.out.print(curNode.val+" ");
            curNode = curNode.next;
        }
        System.out.println();
    }

    // 清空：去掉所有的连接地址-null
    // 目前的问题代码！！！
    public void clear() {
        // 使用头连接指向置空：则无人引用--直接粗暴型！
        System.out.println("使用头连接指向置空：");
        /*Node head = this.head;
        head.next = null;*/
        /// 改：直接将头结点置空！！ 而不是将next置空！！
        this.head = null;


       // 完全置空：先拴住后面，再进行置空
        System.out.println("完全置空：先拴住后面，再进行置空:");

       /* Node head1 = this.head;
        while(head1 != null) {
            Node cur = head1; // 此处错误！！
            head1 = head1.next;
            cur = null;
        }*/

        // 修改：
        Node cur = this.head;
        while(cur != null) {
            Node curNext = cur.next;
            cur.next = null; // 此处又进行区分：是将指向置空！
            cur = curNext;
        }
        // 注意此处：将头结点指向置空之后，头结点的val依旧有保存值，打印时依旧会进行打印，要把头结点也置空
        this.head = null;
    }


    public Node reverseList() {
        // 考虑只有一个结点和没有结点的情况
        if(this.head == null) {
            return null;
        }
        if(this.head.next == null) {
            return head;
        }

        /* 以下是错误代码：
        Node curNext = head.next; //指向第二个结点

        // 因为头结点要变成尾结点，所以next进行置空
        this.head.next = null;

        // 其余结点进行头插：需要进行循环遍历链表实现
        while(curNext != null) {
            // 头插
            Node cur = head;
            head = curNext;
            head.next = cur;   // 此处也有问题：头结点的定义重要的不是变量head，而是指向性问题！！
            curNext = cur.next;  // 思路有问题！！
        }
        */

        // 更正代码：
        Node cur = this.head.next;  // 当前需要反转的节点，head是当前需要反转的节点的前驱！
        this.head.next = null; // 要变成尾结点
        while(cur != null) { // 进行遍历
            Node curNext = cur.next;
            cur.next = this.head;  // 注意此处逻辑！！！
            this.head = cur;
            cur = curNext;
        }

        return head;

    }


    // 寻找中间结点:使用快慢指针
    // fast一次走两步，slow一次走一步：成两倍关系--则快走完慢刚好到中间
    public Node middleNode() {
        // 首先判断是否为空
        if(this.head == null) {
            return null;
        }

        Node slow = this.head;
        // 注意此处错误！！快慢结点都应该从头结点开始！！
        //Node fast = this.head.next;
        Node fast = this.head;
        // 注意此处while语句的循环条件：只要一个不满足就说明已经走到了末指针，即false的短路-逻辑与
        // 注意两个表达式的顺序关系
        while(fast!=null && fast.next!=null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;

        /*// 方法二：求长度+求中间值进行返回结点
        int count = 0;
        Node cur = this.head;
        while((size()/2) != count) {
            cur = cur.next;
            count++;
        }
        return cur;*/
    }

    // 打印结点的值
    public void printNode(Node node) {
        if(node == null) {
            return ;
        }
        System.out.println(node.val);
    }


    // 寻找倒数第k个结点
    public Node findKthToTail(int k) {

        // 方法一：
        /*// 首先进行合法性判断
        if(k<0 || k>size()) { // 注意此处是>size() :因为是从1开始计算数数！
            return null;
        }
        Node fast = this.head;
        Node slow = this.head;
        //fast 先走k-1步 然后再一起走：要求是只遍历一遍

        // 此处另外再定义一个变量会增加空间开辟累赘，直接进行k--就行，让其等于0时候就ok！
        //int count = 0;
        //while(k-1 != count) {
        //    fast = fast.next;
        //}

        // 修改如下：
        while(k-1 != 0) {
            fast = fast.next;
            k--;  // 注意k要进行变换
        }
        // 同时走
        while(fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;*/

        // 方法二：
        // 使用：长度-k
        if (k<0 ) {
            return null;
        }
        /*while(size()-k != 0) {
            this.head = head.next; // 注意这种写法一直报错的原因：this.head改变了，然后size()就会跟着一起发生改变！！
            if(head == null) {
                return null;
            }
            k++;
        }
        return this.head;*/

        // 所以使用临时变量进行修改：
        Node tmp = this.head;
        while (size()-k != 0) {
            tmp = tmp.next;
            if (tmp == null) {
                return null;
            }
            k++;
        }
        return tmp;  // 这样就是head不变！！


        // 方式三：
        /*// 如果没有size（）方法，那可以修改为如下：
        // 首先进行合法性判断
        // 进行一下头结点是否为空的判断，即
        // if(k<0 || this.head==null) { return null; }
        // 之前的有size就不用进行头结点空指针的判断，因为size就直接可以看出null

        if(k<0) {
            return null;
        }
        Node fast = this.head;
        Node slow = this.head;
        //fast 先走k-1步 然后再一起走：要求是只遍历一遍
        while(k-1 != 0) { // 如果k超过长度，会空；但是k-1并不会==0而退出循环
            fast = fast.next;
            if(fast == null) {
                return null; // k太大了就无输出！！
            }
            k--;  // 注意k要进行变换
        }
        // 同时走
        while(fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
       */
    }


    // 以定值x分割链表
    public Node partition (int x) {
        // 分成两段：before 与 after （start & end）
        Node bs = null;
        Node be = null;
        Node as = null;
        Node ae = null;
        // 再创建一个临时结点表示当前结点位置
        Node cur = this.head;

        // 进行遍历
        while(cur != null) {
            if(cur.val<x) {
                // 处于前半段
                if(bs==null) {
                    // 说明现在是一个空结点
                    bs = cur;
                    be = cur;
                } else {
                    be.next = cur;
                    be = be.next; //不要忘记！！
                }
            } else {
                // 处于后半段
                if(as==null) {
                    // 说明现在是一个空结点
                    as = cur;
                    ae = cur;
                } else {
                    ae.next = cur;
                    ae =ae.next;
                }
            }
            cur = cur.next;
        }
        //如果前半段直接没有
        if(bs==null) {
            return as;
        }
        // 如果后半段存在
        if(ae!=null) {
            ae.next = null;
        }
        // 进行连接
        be.next = as;
        this.head = bs;  // 加了这行代码之后才不会发生代码不完整的情况，因为分割后实际指向发生了改变
        return bs;
    }


    // 判断回文链表
    public boolean chkPalindrome() {
        // 找中间结点 + 中间之后进行反转 + 前后遍历 （会使用快慢指针）

        // 进行空判断
        if(head == null) {
            return true;
        }

        // 使用快慢指针找中间位置
        Node slow = this.head;
        Node fast = this.head;
        //while(fast != null) { // 注意这里条件！！！
        // 有奇偶之分，则最后判断条件也是不一样的，同时满足才进行，只要不满足一个就停止
        // 注意两个表达式的顺序！！！
        while (fast!=null && fast.next!=null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        // 此时slow就是中间结点
        // 开始对中间结点之后的节点进行反转 + 建立变量
        Node cur = slow.next;
        while (cur != null) {
            Node curNext = cur.next;
            cur.next = slow;
            //slow = slow.next;// 注意此处是错误的 这样会造成一个循环！！！
            slow = cur; // 直接这样子进行赋值，类似后面的赋值
            cur = curNext;
        }
        // 反转完毕 + 进行回文比较--head与slow分别进行前后遍历
        // 注意循环条件：考虑奇偶：只是简单的head！=slow 只能解决奇数问题，
        // 还有偶数问题就要另外考虑：head<slow不可行，因为是自定义类型，比较大小需要重写比较大小的方法
        // 而偶数问题：head.next == slow 就是需要终止的时候了
        while(this.head!=slow ) {
            if(head.val != slow.val) {
                return false;
            }
            // 在这里进行判断!!! 重要！！！
            if(this.head.next==slow) {
                return true;
            }
            // 进行结点变换
            this.head = head.next;
            slow = slow.next;
        }
        // 来到这儿 说明正确
        return true;
    }


    // 创造一个相交方法：（会存在缺陷，但是就针对本题而言ok）
    public void createCur (Node head1, Node head2) {
        head1.next = head2.next.next;
    }

    // 找到两个链表的相交结点并输出相交结点值
    public Node getIntersectionNode(Node headA,Node headB) {
        // 1. 求两个链表的长度
        int lengthA = 0;
        int lengthB = 0;
        // 创建两个变量，使之一直指向最长和最短链表，然后原来头结点就会保持不变
        Node nodeL = headA; // long
        Node nodeS = headB; // short
        while(nodeL!=null) {
            lengthA++;
            nodeL=nodeL.next;
        }
        while (nodeS!=null) {
            lengthB++;
            nodeS = nodeS.next;
        }
        // 注意要执行后面的各条语句 一定要先把nodeL/S重新处于头结点的位置上！！
        // 而此时并没有在头结点处 如果后面的len判断没有满足条件就会直接执行后面的语句，而此时二者都会处于null上
        // 所以这里语句不能缺少 不然会有错！
        nodeL = headA;
        nodeS = headB;
        // 2. 进行长度比较 + 最长最短的变换
        int len = lengthA-lengthB;
        if(len<0) {
            nodeL = headB;
            nodeS = headA;
            len = lengthB-lengthA;
        }
        // 3. 长的先走差值步
        while(len!=0) {
            nodeL = nodeL.next;
            len--;
        }
        //System.out.println(nodeL.val);
        //System.out.println(nodeS.val);

        // 4. 两个链表同时走
        // 这里写法有问题，就会缺少返回值
       /* while(nodeL!=null) {
            if(nodeL==nodeS) {
                return nodeL;
            }
            nodeL = nodeL.next;
            nodeS = nodeS.next;
        }*/
        // 注意这个还可以再进行修改：还可以更加简便！
        /*while(nodeL!=nodeS && nodeL!=null) { //如果等于就是相交
            nodeL = nodeL.next;
            nodeS = nodeS.next;
        }
        if (nodeS==null) {
            return null;
        }*/
        while(nodeL!=nodeS ) { //如果等于时要么是相交 要么是二者都已经走完为空
            nodeL = nodeL.next;
            nodeS = nodeS.next;
        }
        // 此时来到这里，要么找到交点 要么是遍历完为null，
        // 不管哪种情况都是二者相等的时候，所以随便输出那个都行，况且也不用进行判断

        return nodeL;
    }


    // 设计有环链表
    public void cycle(Node head) {
        Node node = head;
        while(node.next!=null) {
            node = node.next;
        }
        node.next = head.next.next;
    }

    // 判断是否为有环的链表
    public boolean hasCycle() {
        // 快慢指针
        Node fast = this.head;
        Node slow = this.head;
        // 循环判断是否有环 如果相遇就说明有环 如果其中一个（fast or fast.next）是null就说明不存在环
        // 环是没有null的
        /*while(fast.next!=null && fast!=null) { // 注意此处顺序的错误！！！！！
            fast = fast.next.next;  //一次走两步
            slow = slow.next;
            // 放在后面而不是前面的原因，环至少都有两个结点！！
            if(fast == slow) {
                return true;
            }
        }
        // 出来就说明遇到了null
        return false;*/

        // 可以修改如下；
        while( fast!=null && fast.next!=null ) { // 注意顺序，只有fast补null才有next
            fast = fast.next.next;  //一次走两步
            slow = slow.next;
            if(fast == slow) {
                break;
            }
        }
        // 出来有两种情况：要么遇到null 要么相等 进行判断
        // 然后一定要注意这里的判断使用声明进行判断，用是否为null是因为要考虑一个结点的情况：
        // 一个结点时并不是环，环至少要两个结点，如果是用地址相等判断就会为真，
        // 但是实际上是假的，因为while循环的条件中满足其中一个为假直接出来应该为假
        /*if(fast == slow) { // 注意这里的错误！！！
            return true;
        }
        return false;*/
        // 所以进行修改如下：
        if(fast==null || fast.next==null) {
            return false;
        }
        return true;
    }


    // 返回链表开始入环的第一个结点，如果没有环就返回null
    public Node detectCycle() {
        // 先判断有无环：使用快慢指针 +是否相等or遇到null（循环条件）
        Node fast = this.head;
        Node slow = this.head;
        while(fast!=null && fast.next!=null) {
            fast = fast.next.next;
            slow = slow.next;
            // 进行判断
            if(slow==fast) {
                break;
            }
        }
        // 出来就说明要么==null 要么相等
        if(fast==null || fast.next==null) {
            return null;
        }

        // 如果有环在进行前后同时走相遇的操作:走到这里就说明有环,且此时相遇
        slow = this.head;
        while(slow!=fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }


    // 递归实现单链表的逆序打印
    // 判断是否空 or 是否只有一个结点+ 另外
    // 递归：当return后有返回到递归之后开始继续向后执行！！！
    public void printReverse(Node head) {
        // 进行判断：
        if(head==null) {
            return ;
        }
        if(head.next == null) {
            System.out.print(head.val+" ");
            return;
        }
        // 来到这儿说明可以递归：有好多个结点
        printReverse(head.next);
        System.out.print(head.val+" ");
    }


    // 非递归实现逆序打印--模拟栈（先进后出）
    public void printReverseStack(Node head) {
        // 自定义类型存储的是地址！
        // 利用栈的思想
        Stack<Node> stack = new Stack<>(); // 注意是没有传入任何参数的！
        // 定义一个临时变量
        Node cur = head;
        // 首先进行压栈
        while(cur!=null) {
            stack.push(cur);
            cur = cur.next;
        }
        // 再进行弹出
        //while(cur!=null) {// 注意这里循环的条件！！！  --此处是关于栈的！
        while(!stack.empty()) { // 判断是否为空
            Node node = stack.pop(); //直接把节点弹进去
            System.out.print(node.val+" ");
        }
        System.out.println();
    }


}
